/*******************************************************************************
* Copyright (c) 2008, Jeferson Jose de Miranda                                 *
* UDESC - Universidade do Estado de Santa Catarina                             *
* All rights reserved.                                                         *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*     * Redistributions of source code must retain the above copyright         *
*       notice, this list of conditions and the following disclaimer.          *
*     * Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*     * Neither the name of the Universidade do Estado de Santa Catarina nor   *
*       the names of its contributors may be used to endorse or promote        *
*       products derived from this software without specific prior written     *
*       permission.                                                            *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY JEFERSON JOSE DE MIRANDA "AS IS" AND ANY        *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE       *
* DISCLAIMED. IN NO EVENT SHALL JEFERSON JOSE DE MIRANDA BE LIABLE FOR ANY     *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES   *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; *
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF     *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            *
*******************************************************************************/

/**
 * \file Image.hpp
 *
 * \brief MoveIn::Image deals with IplImage (OpenCV's image format) basic
 * functions.
 *
 * \author Jeferson Jose de Miranda
 *
 * This class will set a layer of abstraction over the OpenCV's IplImage,
 * so that most of its basic functionality, like creating, deleting or
 * accessing its pixels can be done in an easy way.
 */

#ifndef IMAGE_HPP
#define IMAGE_HPP

// (OpenCV) cv.h holds the OpenCV IplImage definitions.
#include <cv.h>
// MoveIn::RGBColor manipulates RGB values.
#include "RGBColor.hpp"
// Exceptions.hpp groups Move-In exception messages.
#include "Exceptions.hpp"

namespace MoveIn
{
    /**
    * \class Image
    *
    * \brief Basic IplImage functions.
    *
    * This class will set a layer of abstraction over the OpenCV IplImage,
    * so that most of its basic functionality, like creating, deleting or
    * accessing its pixels can be done without have to know much about the
    * IplImage structure.
    */
    class Image
    {
        protected:
        /**
        * \var IplImage *image
        *
        * \brief The image structure used in OpenCV.
        */
        IplImage *image;

        public:
        /**
        * \fn Image::Image(unsigned short int,
                           unsigned short int,
                           unsigned short int,
                           unsigned short int)
        *
        * \brief The image constructor takes no default values.
        *
        * The constructor of MoveIn::Image takes four parameters: image width,
        * image height, color depth and number of channels.
        *
        * \param width
        * The horizontal size of the image.
        * \n Examples of common sizes for low cost webcams are:
        * \n 128 (Sub-QCIF)
        * \n 320 (QVGA)
        * \n 640 (VGA)
        *
        * \param height
        * The vertical size of the image.
        * \n Examples of common sizes for low cost webcams are:
        * \n 96 (Sub-QCIF)
        * \n 240 (QVGA)
        * \n 480 (VGA)
        *
        * \param depth
        * The pixel depth in bits. This is defined by OpenCV, and can one of
        * the folowing values:
        * \n IPL_DEPTH_8U: 8 bit unsigned integer;
        * \n IPL_DEPTH_8S: 8 bit integer;
        * \n IPL_DEPTH_16U: 16 bit unsigned integer;
        * \n IPL_DEPTH_16S: 16 bit integer;
        * \n IPL_DEPTH_32S: 32 bit integer;
        * \n IPL_DEPTH_32F: 32 bit float;
        * \n IPL_DEPTH_64F: 64 bit float.
        *
        * \param channels the number of color channels, or layers, in an image.
        */
        Image(unsigned short int width,
              unsigned short int height,
              unsigned short int depth,
              unsigned short int channels);

        /**
        * \fn Image::Image(IplImage*)
        *
        * \brief Creates an image by clonnig the IplImage source.
        *
        * \param source
        * A referece to IplImage source.
        */
        Image(IplImage *source);

        /**
        * \fn Image::~Image()
        *
        * \brief The image destructor.
        *
        * The destructor releases the alocated IplImage.
        */
        ~Image();

        /**
        * \fn Image::reset()
        *
        * \brief Set the image pixels to 0.
        */
        void reset();

        /**
        * \fn Image::copyImage(Image*)
        *
        * \brief Copies the data in source image to this instance of Image.
        *
        * \param source
        * A pointer to source MoveIn::Image.
        */
        void copyImage(Image *source);

        /**
        * \fn Image::copyImage(IplImage*)
        *
        * \brief Copies the data in source image to this instance of IplImage.
        *
        * \param source
        * A pointer to source IplImage.
        */
        void copyImage(IplImage *source);

        /**
        * \fn Image::cloneImage(Image*)
        *
        * \brief Clones the data in source image. The original data and
        * parameters of this IplImage will be erased. New data and parameters
        * will be set to match the the contents in the source Image.
        *
        * \param source
        * A pointer to source MoveIn::Image.
        */
        void cloneImage(Image *source);

        /**
        * \fn Image::cloneImage(IplImage*)
        *
        * \brief Clones the data in source image. The original data and
        * parameters of this IplImage will be erased. New data and parameters
        * will be set to match the the contents in the source IplImage.
        *
        * \param source
        * A pointer to source IplImage.
        */
        void cloneImage(IplImage *source);

        /**
        * \fn Image::getIplImage()
        *
        * \brief Returns a pointer to IplImage.
        *
        * \return Pointer to OpenCV's IplImage.
        */
        IplImage* getIplImage();

        /**
        * \fn Image::invert()
        *
        * \brief Verticaly flips the image.
        */
        void invert();

        /**
        * \fn Image::mirror()
        *
        * \brief Horizontaly flips the image.
        *
        */
        void mirror();

        /**
        * \fn Image::invertAndMirror()
        *
        * \brief Flips the image verticaly and horizontaly.
        */
        void invertAndMirror();

        /**
        * \fn Image::getWidth()
        *
        * \brief Returns the image width.
        *
        * \return Image width.
        */
        unsigned short int getWidth();

        /**
        * \fn Image::getHeight()
        *
        * \brief Returns the image height.
        *
        * \return Image height.
        */
        unsigned short int getHeight();

        /**
        * \fn Image::getHeight()
        *
        * \brief Returns the number of channels.
        *
        * \return Number of channels in the image.
        */
        unsigned short int getChannels();

        /**
        * \fn Image::getData()
        *
        * \brief Returns the image data stored in an array of chars. Usefull to
        * display the image in window managers other than OpenCV's highgui.
        *
        * \return Image data, an array of signed chars.
        */
        char* getData();

        /**
        * \fn Image::isEquivalent(Image*, Image*)
        *
        * \brief Checks the equivalence with the source images by comparing
        * basic attributes (width, height, depth and number of channels).
        *
        * \param img1
        * A pointer to MoveIn::Image
        *
        * \param img2
        * A pointer to MoveIn::Image
        *
        * \return True, to images with the same basic attributes, and false
        * if at least one of these attributes differ.
        */
        static bool isEquivalent(Image *img1, Image *img2);

        /**
        * \fn Image::isEquivalent(IplImage*, IplImage*)
        *
        * \brief Checks the equivalence with the source images by comparing
        * basic attributes (width, height, depth and number of channels).
        *
        * \param img1
        * A pointer to OpenCV's IplImage
        *
        * \param img2
        * A pointer to OpenCV's IplImage
        *
        * \return True, to images with the same basic attributes, and false
        * if at least one of these attributes differ.
        */
        static bool isEquivalent(IplImage *img1, IplImage *img2);

        /**
        * \fn Image::isSameSize(Image*, Image*)
        *
        * \brief Checks if the source images have the same width and height.
        *
        * \param img1
        * A pointer to MoveIn::Image
        *
        * \param img2
        * A pointer to MoveIn::Image
        *
        * \return True, if the images have same size, false if not.
        */
        static bool isSameSize(Image *img1, Image *img2);

        /**
        * \fn Image::isSameSize(IplImage*, IplImage*)
        *
        * \brief Checks if the source images have the same width and height.
        *
        * \param img1
        * A pointer to OpenCV's IplImage
        *
        * \param img2
        * A pointer to OpenCV's IplImage
        *
        * \return True, if the images have same size, false if not.
        */
        static bool isSameSize(IplImage *img1, IplImage *img2);

        void setROI(CvRect *ROI);

        CvRect getROI();

        void resetROI();
    };

/*******************************************************************************
* MoveIn::Image::Image                                                         *
*******************************************************************************/

    Image::Image(unsigned short int width,
                 unsigned short int height,
                 unsigned short int depth,
                 unsigned short int channels)
    {
        image = NULL;
        image = cvCreateImage(cvSize(width, height), depth, channels);
        if(image == NULL)
        {
            throw new OutOfMemory();
        }
        else
        {
            reset();
            //cvZero(image);
        }
    }

/*******************************************************************************
* MoveIn::Image::Image                                                         *
*******************************************************************************/

    Image::Image(IplImage *source)
    {
        image = NULL;
        image = cvCreateImage(cvSize(source->width, source->height),
                              source->depth,
                              source->nChannels);
        if(image == NULL)
        {
            throw new OutOfMemory;
        }
        else
        {
            reset();
            //cvZero(image);
        }
    }

/*******************************************************************************
* MoveIn::Image::~Image                                                        *
*******************************************************************************/

    Image::~Image()
    {
        cvReleaseImage(&image);
    }

/*******************************************************************************
* MoveIn::Image::reset                                                         *
*******************************************************************************/

    void Image::reset()
    {
        cvZero(image);
    }

/*******************************************************************************
* MoveIn::Image::copyImage                                                     *
*******************************************************************************/

    void Image::copyImage(Image *source)
    {
        if(isEquivalent(this, source))
        {
            cvCopyImage(source->getIplImage(), image);
        }
        else
        {
            throw new DifferentType();
        }
    }

/*******************************************************************************
* MoveIn::Image::copyImage                                                     *
*******************************************************************************/

    void Image::copyImage(IplImage *source)
    {
        if(isEquivalent(this->getIplImage(), source))
        {
            cvCopyImage(source, image);
        }
        else
        {
            throw new DifferentType();
        }
    }

/*******************************************************************************
* MoveIn::Image::cloneImage                                                    *
*******************************************************************************/

    void Image::cloneImage(Image *source)
    {
        if(image != NULL)
            cvReleaseImage(&image);
        image = cvCloneImage(source->getIplImage());
    }

/*******************************************************************************
* MoveIn::Image::cloneImage                                                    *
*******************************************************************************/

    void Image::cloneImage(IplImage *source)
    {
        cvReleaseImage(&image);
        image = cvCloneImage(source);
    }

/*******************************************************************************
* MoveIn::Image::getIplImage                                                   *
*******************************************************************************/

    IplImage* Image::getIplImage()
    {
        return image;
    }

/*******************************************************************************
* MoveIn::Image::invert                                                        *
*******************************************************************************/

    void Image::invert()
    {
        cvFlip(image, image, 0);
    }

/*******************************************************************************
* MoveIn::Image::mirror                                                        *
*******************************************************************************/

    void Image::mirror()
    {
        cvFlip(image, image, 1);
    }

/*******************************************************************************
* MoveIn::Image::invertAndMirror                                               *
*******************************************************************************/

    void Image::invertAndMirror()
    {
        cvFlip(image, image, -1);
    }

/*******************************************************************************
* MoveIn::Image::getWidth                                                      *
*******************************************************************************/

    unsigned short int Image::getWidth()
    {
        return image->width;
    }

/*******************************************************************************
* MoveIn::Image::getHeight                                                     *
*******************************************************************************/

    unsigned short int Image::getHeight()
    {
        return image->height;
    }

/*******************************************************************************
* MoveIn::Image::getChannels                                                   *
*******************************************************************************/

    unsigned short int Image::getChannels()
    {
        return image->nChannels;
    }

/*******************************************************************************
* MoveIn::Image::getData                                                       *
*******************************************************************************/

    char* Image::getData()
    {
        return image->imageData;
    }

/*******************************************************************************
* MoveIn::Image::isEquivalent                                                  *
*******************************************************************************/

    bool Image::isEquivalent(Image *img1, Image *img2)
    {
        if(img1->getIplImage()->width     == img2->getIplImage()->width  &&
           img1->getIplImage()->height    == img2->getIplImage()->height &&
           img1->getIplImage()->depth     == img2->getIplImage()->depth  &&
           img1->getIplImage()->nChannels == img2->getIplImage()->nChannels)
        {
            return true;
        }
        return false;
    }

/*******************************************************************************
* MoveIn::Image::isEquivalent                                                  *
*******************************************************************************/

    bool Image::isEquivalent(IplImage *img1, IplImage *img2)
    {
        if(img1->width     == img2->width  &&
           img1->height    == img2->height &&
           img1->depth     == img2->depth  &&
           img1->nChannels == img2->nChannels)
        {
            return true;
        }
        return false;
    }

/*******************************************************************************
* MoveIn::Image::isSameSize                                                    *
*******************************************************************************/

    bool Image::isSameSize(Image *img1, Image *img2)
    {
        if(img1->getIplImage()->width  == img2->getIplImage()->width  &&
           img1->getIplImage()->height == img2->getIplImage()->height)
        {
            return true;
        }
        return false;
    }

/*******************************************************************************
* MoveIn::Image::isSameSize                                                    *
*******************************************************************************/

    bool Image::isSameSize(IplImage *img1, IplImage *img2)
    {
        if(img1->width  == img2->width  &&
           img1->height == img2->height)
        {
            return true;
        }
        return false;
    }

/*******************************************************************************
* MoveIn::Image::setROI                                                        *
*******************************************************************************/

    void Image::setROI(CvRect *ROI)
    {
        if(ROI->x < 0 ||
           ROI->y < 0 ||
           ROI->x + ROI->width > getWidth() ||
           ROI->y + ROI->height > getHeight())
        {
            throw new ROIOutOfBounds;
        }
        else
        {
            cvSetImageROI(getIplImage(), *ROI);
        }
    }

/*******************************************************************************
* MoveIn::Image::getROI                                                        *
*******************************************************************************/

    CvRect Image::getROI()
    {
        return cvGetImageROI(image);
    }

/*******************************************************************************
* MoveIn::Image::resetROI                                                      *
*******************************************************************************/

    void Image::resetROI()
    {
        cvResetImageROI(image);
    }
}

#endif
